// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build aix | | solaris 

// 此文件为使用aix或solaris等libc系统调用的操作系统处理forkAndExecInChild函数。

package syscall

import (
	"runtime"
	"unsafe"
)

type SysProcAttr struct {
	Chroot     string      // Chroot。
	Credential *Credential // 凭证。
	Setsid     bool        // 创建会话。
	// Setpgid将子进程的进程组ID设置为Pgid，
	// 或者，如果Pgid==0，则设置为新子进程的进程ID。
	Setpgid bool
	// Setctty将子进程的控制终端设置为
	// 文件描述符Ctty。Ctty必须是子进程中的描述符编号
	// ：procatr的索引。文件夹。
	// 只有当Setsid为true时，这才有意义。
	Setctty bool
	Noctty  bool // 从控制终端分离fd 0 
	Ctty    int  // 控制TTY fd 
	// 前台将子进程组放置在前台。
	// 这意味着Setpgid。Ctty字段必须设置为
	// 控制TTY的描述符。
	// 与Setctty不同，在本例中，Ctty必须是父进程中的描述符
	// 编号。
	Foreground bool
	Pgid       int // 如果设置了PGID，则为子进程组ID。
}

// 在运行时包中实现。
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()

func chdir(path uintptr) (err Errno)
func chroot1(path uintptr) (err Errno)
func close(fd uintptr) (err Errno)
func dup2child(old uintptr, new uintptr) (val uintptr, err Errno)
func execve(path uintptr, argv uintptr, envp uintptr) (err Errno)
func exit(code uintptr)
func fcntl1(fd uintptr, cmd uintptr, arg uintptr) (val uintptr, err Errno)
func forkx(flags uintptr) (pid uintptr, err Errno)
func getpid() (pid uintptr, err Errno)
func ioctl(fd uintptr, req uintptr, arg uintptr) (err Errno)
func setgid(gid uintptr) (err Errno)
func setgroups1(ngid uintptr, gid uintptr) (err Errno)
func setsid() (pid uintptr, err Errno)
func setuid(uid uintptr) (err Errno)
func setpgid(pid uintptr, pgid uintptr) (err Errno)
func write1(fd uintptr, buf uintptr, nbyte uintptr) (n uintptr, err Errno)

// syscall代表我们定义了这个全局变量，以避免对其他平台的生成依赖
func init() {
	execveLibc = execve
}

// Fork，dup fd to 0。。len（fd）和exec（argv0、argvv、envv）在child中。
// 如果dup或exec失败，请将errno错误写入管道。
// （管道在exec上关闭，因此如果exec成功，它将关闭。）
// 在子函数中，此函数不能获取任何锁，因为
// 它们可能在分叉时已被锁定。这意味着
// 没有重新调度，没有malloc调用，也没有新的堆栈段。
// 
// 我们称之为手工编制的系统调用，在
// 运行时/系统调用\u solaris。go，而不是生成的libc包装
// 因为我们需要避免延迟加载函数（可能是malloc、
// 拆分堆栈或获取互斥）。我们不能调用RawSyscall 
// 因为即使对于BSD子系统调用也不安全。
// go:norace 
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
	// 在顶部声明所有变量，以防任何
	// 声明需要堆分配（例如err1）。
	var (
		r1     uintptr
		err1   Errno
		nextfd int
		i      int
	)

	// 注意下面的fds洗牌的副作用。
	// 确保nextfd超出任何当前打开的文件，这样我们就不会冒覆盖任何文件的风险。
	fd := make([]int, len(attr.Files))
	nextfd = len(attr.Files)
	for i, ufd := range attr.Files {
		if nextfd < int(ufd) {
			nextfd = int(ufd)
		}
		fd[i] = int(ufd)
	}
	nextfd++

	// 即将呼叫fork。
	// 不再分配或调用非汇编函数。
	runtime_BeforeFork()
	r1, err1 = forkx(0x1) // FORK_NOSIGCHLD 
	if err1 != 0 {
		runtime_AfterFork()
		return 0, err1
	}

	if r1 != 0 {
		// 父母；return PID 
		runtime_AfterFork()
		return int(r1), 0
	}

	// Fork成功，现在在child中。

	// 会话ID 
	if sys.Setsid {
		_, err1 = setsid()
		if err1 != 0 {
			goto childerror
		}
	}

	// 设置进程组
	if sys.Setpgid || sys.Foreground {
		// 将子进程放入进程组。
		err1 = setpgid(0, uintptr(sys.Pgid))
		if err1 != 0 {
			goto childerror
		}
	}

	if sys.Foreground {
		pgrp := _Pid_t(sys.Pgid)
		if pgrp == 0 {
			r1, err1 = getpid()
			if err1 != 0 {
				goto childerror
			}

			pgrp = _Pid_t(r1)
		}

		// 将流程组放在前台。
		err1 = ioctl(uintptr(sys.Ctty), uintptr(TIOCSPGRP), uintptr(unsafe.Pointer(&pgrp)))
		if err1 != 0 {
			goto childerror
		}
	}

	// 恢复信号掩码。我们在TIOCSPGRP之后这样做是为了避免
	// 让内核向进程组发送SIGTTOU信号。
	runtime_AfterForkInChild()

	// Chroot 
	if chroot != nil {
		err1 = chroot1(uintptr(unsafe.Pointer(chroot)))
		if err1 != 0 {
			goto childerror
		}
	}

	// User and groups 
	if cred := sys.Credential; cred != nil {
		ngroups := uintptr(len(cred.Groups))
		groups := uintptr(0)
		if ngroups > 0 {
			groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
		}
		if !cred.NoSetGroups {
			err1 = setgroups1(ngroups, groups)
			if err1 != 0 {
				goto childerror
			}
		}
		err1 = setgid(uintptr(cred.Gid))
		if err1 != 0 {
			goto childerror
		}
		err1 = setuid(uintptr(cred.Uid))
		if err1 != 0 {
			goto childerror
		}
	}

	// Chdir 
	if dir != nil {
		err1 = chdir(uintptr(unsafe.Pointer(dir)))
		if err1 != 0 {
			goto childerror
		}
	}

	// Pass 1：寻找fd[i]<i，并将其移到len（fd）
	// 上方，这样Pass 2就不会踩到以后需要的fd上。
	if pipe < nextfd {
		switch runtime.GOOS {
		case "illumos":
			_, err1 = fcntl1(uintptr(pipe), _F_DUP2FD_CLOEXEC, uintptr(nextfd))
		default:
			_, err1 = dup2child(uintptr(pipe), uintptr(nextfd))
			if err1 != 0 {
				goto childerror
			}
			_, err1 = fcntl1(uintptr(nextfd), F_SETFD, FD_CLOEXEC)
		}
		if err1 != 0 {
			goto childerror
		}
		pipe = nextfd
		nextfd++
	}
	for i = 0; i < len(fd); i++ {
		if fd[i] >= 0 && fd[i] < int(i) {
			if nextfd == pipe { // 不要在管道上跺脚
				nextfd++
			}
			switch runtime.GOOS {
			case "illumos":
				_, err1 = fcntl1(uintptr(fd[i]), _F_DUP2FD_CLOEXEC, uintptr(nextfd))
			default:
				_, err1 = dup2child(uintptr(fd[i]), uintptr(nextfd))
				if err1 != 0 {
					goto childerror
				}
				_, err1 = fcntl1(uintptr(nextfd), F_SETFD, FD_CLOEXEC)
			}
			if err1 != 0 {
				goto childerror
			}
			fd[i] = nextfd
			nextfd++
		}
	}

	// 将2:dup fd[i]传递到i上。
	for i = 0; i < len(fd); i++ {
		if fd[i] == -1 {
			close(uintptr(i))
			continue
		}
		if fd[i] == int(i) {
			// dup2（i，i）在Linux上不会清除close on exec标志，
			// 在其他地方可能也不会。
			_, err1 = fcntl1(uintptr(fd[i]), F_SETFD, 0)
			if err1 != 0 {
				goto childerror
			}
			continue
		}
		// 新的fd是在exec上创建的，
		// 这正是我们想要的。
		_, err1 = dup2child(uintptr(fd[i]), uintptr(i))
		if err1 != 0 {
			goto childerror
		}
	}

	// 按照惯例，我们不会关闭我们的fds，所以如果len（fd）<3，根据需要关闭0,1,2。
	// 知道自己继承了fds>=3的程序需要
	// 才能在exec上关闭它们。
	for i = len(fd); i < 3; i++ {
		close(uintptr(i))
	}

	// 从tty中分离fd 0 
	if sys.Noctty {
		err1 = ioctl(0, uintptr(TIOCNOTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 将控制tty设置为Ctty 
	if sys.Setctty {
		// 在AIX上，TIOCSCTTY未定义
		if TIOCSCTTY == 0 {
			err1 = ENOSYS
			goto childerror
		}
		err1 = ioctl(uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 执行时间。
	err1 = execve(
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))

childerror:
	// 在管道上发送错误代码
	write1(uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
	for {
		exit(253)
	}
}
